【新機能】 #SORACOM EndorseによるSIMカードとAWSの認証連携
ども、大瀧です。
SORACOM Conference 2016 "Connected."でSORACOMの新サービスがいくつか発表されました。そのうちの一つであるSORACOM Endorseは、SORACOM Air専用のトークン発行サービスです。 Endorseの発行するトークンはSIMカードと対応付けることができるため、トークンに対応する外部Webサービスに送ることで、SIMカードと外部Webサービスの認証連携を構成することができます。今回は、おなじみAWS APIとの認証連携の例をご紹介します。
概要
SORACOM Endorseは、SORACOM所有の秘密鍵を用いたJWT(JSON Web Token)形式の電子署名済みトークンを発行します。(JWTについては都元のブログ記事が詳しいので、JWTの説明はここでは割愛します。)署名対象となるJSONには、検証用公開鍵ファイル名とIMSIなどAir SIMの情報 *1が含まれるので、検証するWebサービスでは公開鍵による署名自体の正当性だけでなく、SIMの正当性、ユーザー独自情報による正当性の検証も組み込む拡張性があります。今回はWebサービスのサンプルとしてAmazon API Gateway + AWS LambdaでSIMの正当性の検証を実装してみました。
Endorseの構成
EndorseはSORACOMのグループ単位で設定します。ユーザーコンソールとAPIで設定することができますが、今回はSORACOM APIをSORACOM SDK for RubyのCLIから設定します。グループ設定のSoracomEndorse
ネームスペースにパラメータとして以下をkey
とvalue
のハッシュで指定します。
$ export SORACOM_EMAIL=YOUR_SORACOM_EMAIL $ export SORACOM_PASSWORD=YOUR_SORACOM_PASSWORD $ soracom auth $ soracom group update_configuration \ --group-id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX \ --namespace=SoracomEndorse \ --params='[{"key":"parametersToEndorse","value":{"imsi":true,"imei":true,"msisdn":false,"userdata":true}},{"key":"enabled","value":true},{"key":"tokenTimeoutSeconds","value":600}]' { "operatorId": "OP1234567890", "groupId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "createdAt": 1453179246724, "lastModifiedAt": 1453282193396, "configuration": { "SoracomEndorse": { "parametersToEndorse": { "imsi": true, "imei": true, "msisdn": false, "userdata": true }, "enabled": true, "tokenTimeoutSeconds": 600 } }, "tags": { "name": "OtakiTest" }, "createdTime": 1453179246724, "lastModifiedTime": 1453282193396 } $
これでグループ設定はOKです。利用したいSIMをグループに加え、SIMを差したマシン(SIMを差したWi-Fiルータ経由などでも可)で動作確認してみます。Endorseのエンドポイントendorse.soracom.io
にHTTPSでGETリクエストを送り、{"token":<トークン文字列>}
というJSONが返ってくればOKです。
$ curl https://endorse.soracom.io {"token":"eyJraWQiOiJ2MS1mMmZlYTA2MGI5M2Y1MTBiZmI3MjJmMmNkNGIzNzc0ZS14NTA5LnBlbSIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJodHRwczovL3NvcmFjb20uaW8iLCJhdWQiOiJzb3JhY29tLWVuZG9yc2UtYXVkaWVuY2UiLCJleHAiOjE0NTM4MjA1ODAsImp0aSI6Il9wbmxhR3g5RGlCNFBFUXF5aW1XSUEiLCJpYXQiOjE0NTM4MjA0MDAsIm5iZiI6MTQ1MzgyMDM0MCwic3ViIjoic29yYWNvbS1lbmRvcnNlIiwic29yYWNvbS1lbmRvcnNlLWNsYWltIjp7Imltc2kiOiI0NDAxMDMxNDQ4NDM3OTgiLCJpbWVpIjoiMzU1MjMwMDQzMzkzNDY1In19.Uv4RXiyD2MXTJesrcKiZSHCfhLJKAt_ug6SrCSsM4PfHM9kbrbCxieZZuS2ATi2ynisW-25-QysZQScQO12JpDinXk6RdTNW6kM2YdTzH3KcWiLt-nlXYwemGdnrRtSBCVcL10tep6Jemla-XeO-mJYT5XLjFWN8aX4sSYi_xunZcg7LIvuPqQIqQT8SahRPbWO31EVTEsiHnelLWNpkQUAehIYb3rU3wiSPzxJ-ZqEybA3VlxFwxruqYgmQTztGZSQugNtfmU_69cwV-gK9SxY79zO7LsLsPjwIL2MloAFYGci5Wg1ealJoBpq51j9KRUnNGe_3E8wzYw914gSPGA"}$
"Use SORACOM Air to access SORACOM Endorse server."
というレスポンスが返る場合は、Endorseの設定に不備があるか、SIMを経由せずにEndorseのエンドポイントにアクセスしています。それぞれ確認しましょう。
トークンは、JWTに対応する各言語のライブラリでデコードできますが、Webアプリケーションとして提供されるJSON Web Tokens - jwt.ioがお手軽です。画面左側のEncoded
のフォームにトークンをペーストすると、右側のDecoded
にデコード内容が表示されます。
header.kid
が検証用公開鍵のファイル名、payload.soracom-endorse-claim.*
にSIM関連の情報が見えますね。このあとの検証ロジックでそれぞれ参照します。
AWSの構成
Webサービスの簡単なサンプルとして、API Gateway + Lambda(Node.js)で実装してみました。以下のような処理をLambdaで実装します。
IAMロールの設定
ユーザーにレスポンスとして与える一時キーに対応するIAMロールとLambda実行用のロールを用意します。今回は検証用なので、1つのロールにまとめて定義しました。以下のインラインポリシー2つを紐付けるlambda_exec_role_assume_role_myself
ロールを追加しました。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:*" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject" ], "Resource": [ "arn:aws:s3:::*" ] } ] }
{ "Version": "2012-10-17", "Statement": [ { "Sid": "Stmt1453184220000", "Effect": "Allow", "Action": [ "sts:AssumeRole" ], "Resource": [ "*" ] } ] }
また、作成したロールの[信頼関係]タブで以下のポリシーを設定します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com", "AWS": "arn:aws:iam::<AWSアカウントID>:root" }, "Action": "sts:AssumeRole" } ] }
これでOKです。
API Gatewayの設定
今回はAPI GatewayをLambdaを呼び出すディスパッチャとして利用するだけなので、特別な設定は特にしません。/
リソースのGET
メソッドに定義、後述のLambda関数をIntegration Typeに設定しました。トークンを渡すクエリストリングsoracom_endorse_token
がLambdaのイベントオブジェクトのtoken
メンバーとして渡るよう、[Method Request] - [URL Query String Parameters]と[Method Integration] - [Mapping Templates]を以下のように設定しました。
これでOKです。デプロイしておきます。
Lambda関数の定義
今回はNode.jsで実装してみました。
var request = require('request'); var aws = require('aws-sdk'); var jwt = require('jsonwebtoken'); var soracom = require('soracom'); // SORACOMの認証情報 var sora_email = 'YOUR_SORACOM_EMAIL'; var sora_password = 'YOUR_SORACOM_PASSWORD'; // SORACOM Endorseの検証用公開キーストア var sora_keystore = 'https://s3-ap-northeast-1.amazonaws.com/soracom-public-keys/'; // 一時クレデンシャルの対象となるAWS IAMロールのARN var role_arn = 'arn:aws:iam::123456789012:role/ROLE_NAME'; console.log('Loading event'); exports.handler = function(event, context) { // トークンをデコード var decoded = jwt.decode(event.token, {complete: true}); // デコードしたペイロードからIMSIを取得 var imsi = decoded.payload['soracom-endorse-claim'].imsi; // ヘッダに含まれるキー名からキーストアのURLを生成し、取得 request(sora_keystore + decoded.header.kid, function (err, response, body){ var pubkey = body; try { // トークンの検証が成功すればtryブロックを継続 jwt.verify(event.token, pubkey); // SORACOM APIにアクセス var sora_obj = new soracom({ email: sora_email, password: sora_password }); sora_obj.post('/auth', function(err, res, auth) { if (!err) { sora_obj.defaults(auth); sora_obj.get('/subscribers', function(err, res, body) { // SIM一覧から、IMSIを照合 var result = body.some(function(subscriber) { return subscriber.imsi == imsi; }) if(result) { // IMSIの照合が成功したら、AWSの一時キーを取得 var sts = new aws.STS(); var params = { RoleArn: role_arn, RoleSessionName: 'nominator-temp' }; sts.assumeRole(params, function (err, data) { if (!err) { // 一時キーを含むレスポンスを返送 context.succeed(data); } else { console.log(err, err.stack); context.fail('ERROR : Failed to get credential.'); } }); }else { // IMSI照合に失敗した場合 context.fail('ERROR : Not authorized your device.'); }; }); } }); } catch(err) { // トークンの検証に失敗した場合 console.log(err, err.stack); context.fail('ERROR : Failed to verify web token.'); } }); };
17〜26行目
: JWTのデコード、検証を行っています。Node.jsのJWTライブラリは扱いが非常に楽でシンプルに実装できた印象です。検証用公開鍵のURLは公開鍵ファイル名を除き固定です。28〜37行目
: SORACOM APIのSIM一覧をコールし、デコードしたトークンに含まれるISMIと照合しています。こちらの記事と同様の処理です。45行目
でAWSの一時キーを発行、48行目
で返しています。
動作確認
では、Endorseが発行したトークンをクエリストリングに含め、作成したWebサービス(API Gatewayのエンドポイント)にリクエストしてみます。
$ curl https://1234567890.execute-api.ap-northeast-1.amazonaws.com/prod/?soracom_endorse_token=XXXXXXXXXXXXXXXXXXXXXXX {"ResponseMetadata":{"RequestId":"fd9d903a-c444-11e5-8cf2-d5f82273d3d6"},"Credentials":{"AccessKeyId":"ASIAXXXXXXXXXXXX","SecretAccessKey":"XXXXXXXXXXXXXXXXXXXXXXXX","SessionToken":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","Expiration":"2016-01-26T16:53:49.000Z"},"AssumedRoleUser":{"AssumedRoleId":"AROAJTSYXEA5LVZ6B6N5S:nominator-temp","Arn":"arn:aws:sts::XXXXXXXXXXXX:assumed-role/lambda_exec_role_assume_role_myself/nominator-temp"}} $
見慣れた方には見慣れている、AWSの一時キーが返ってきました!
AssumeRole
APIのレスポンスはAWS CLIのローカルキャッシュのJSONと互換性がありそうなことに気づいたので、AWS CLIの設定ファイルにプロファイルを設定し、以下のようにローカルキャッシュにリダイレクトすることで事前の権限設定なしでAWS CLIを使えることを確認しました。
: [endorse] role_arn = arn:aws:iam::XXXXXXXXXXXX:role/lambda_exec_role_assume_role_myself source_profile = default
$ curl https://1234567890.execute-api.ap-northeast-1.amazonaws.com/prod/?soracom_endorse_token=XXXXXXXXXXXXXXXXXXXXXXX \ > ~/.aws/cli/cache/endorse--arn_aws_iam__XXXXXXXXXXXX_role-lambda_exec_role_assume_role_myself.json $ aws logs describe-log-groups \ --profile endorse \ --region ap-northeast-1 { "logGroups": [ { "arn": "arn:aws:logs:ap-northeast-1:XXXXXXXXXXXX:log-group:/aws/lambda/Nominator:*", "creationTime": 1453184914154, "metricFilterCount": 0, "logGroupName": "/aws/lambda/Nominator", "storedBytes": 24480 } ] } $
いい感じです。
まとめ
SORACOMの新しい認証サービスEndorseとAWS APIを連携させる例を紹介しました。もちろんAWS以外の任意のWebサービスでトークンのデコード/検証ができますので、様々なWebサービスがEndorseに対応していくことを期待します。
脚注
- そのほか、ユーザーデータとしてデバイスから任意の情報を追加することもできます ↩